package com.silence.gmall.item.service.impl;

import com.silence.gmall.annotation.MallCache;
import com.silence.gmall.base.Result;
import com.silence.gmall.component.CatchOpsComponent;
import com.silence.gmall.constant.ProductRedisStringConstant;
import com.silence.gmall.item.feign.SkuDetailFeignClient;
import com.silence.gmall.item.service.SkuDetailService;
import com.silence.gmall.product.dto.SkuInfoDto;
import com.silence.gmall.product.dto.SpuSaleAttrDto;
import com.silence.gmall.web.dto.CategoryViewDto;
import com.silence.gmall.web.vo.res.SkuDetailResVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;

/**
 * CompletableFuture： 异步编排
 * 1、怎么启动一个异步任务：
 *      1)、X 继承 Thread; new Haha().start()
 *      2)、X 实现 Runnable；   new Thread(new Hehe()).start();
 *      3)、Callable：  有返回值，有异常
 *                      FutureTask<Integer> task = new FutureTask<>(new Heihei());
 *                      new Thread(task).start();
 *      4)、线程池: ExecutorService
 *          submit():  提交一个带返回值Future的任务； future.get();//等待任务执行完
 *          execute(): 提交一个返回void任务
 *
 *      5)、异步编排: CompletableFuture
 *          1）、启动一个异步的方式：
 *            runAsync(runnable)：  CompletableFuture<Void>            以异步方式启动一个任务并在默认的线程池(ForkJoinPool)执行。 返回 void
 *            runAsync(runnable,executor):CompletableFuture<Void>    以异步方式启动一个任务并在指定的线程池(executor)执行。 返回 void
 *                                                              可以调用future.get()等待异步结束
 *            supplyAsync(supplier): CompletableFuture<U>   以异步方式启动一个任务并在默认的线程池(ForkJoinPool)执行。返回异步结果
 *            supplyAsync(supplier,executor):CompletableFuture<U>  以异步方式启动一个任务并在指定的线程池(executor)执行。返回异步结果
 *
 *          2）、编排 then
 *              1)、future.get();//等待异步任务执行完，并获取结果
 *              2)、future.whenComplete(); 当异步结束后做一件事情.
 *                      正常完成、异常完成
 *              3)、future = future.exceptionally()； 异常兜底：
 *              4)、thenXXX：接下来干什么
 *                    1）、thenRun(runnable):                 接下来跑一个任务，以当前线程作为跑任务的线程，不开额外的异步线程
 *                    2）、thenRunAsync(runnable):            接下来跑一个任务，用默认线程池新开一个异步线程，
 *                    3）、thenRunAsync(runnable,executor):   接下来跑一个任务，用指定线程池新开一个异步线程，
 *                    ===============================================
 *                    1）、thenAccept(consumer):              接下来跑一个任务，接受到上次的结果，以当前线程作为跑任务的线程，
 *                    2）、thenAcceptAsync(consumer)
 *                    3）、thenAcceptAsync(consumer,executor)
 *                    ===============================================
 *                    1）、thenApply(function):
 *                    2）、thenApplyAsync(function):
 *                    3）、thenApplyAsync(function,executor):
 *                    =====================================
 *                    thenCombine: 结合一个任务
 *        3)、组合多任务
 *              allOf();
 *              anyOf();
 *              thenCombine
 *              runAfterBoth
 *
 *
 *
 *  启动一个异步任务，开始编写它的编排逻辑；
 *
 * @Author silence
 * @Date 2025-08-28 16:21
 * @Email 792245022@qq.com
 * @Version 1.0
 * @Description
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SkuDetailServiceImpl implements SkuDetailService {

    /**
     * SkuDetailFeignClient 注入
     */
    private final SkuDetailFeignClient skuDetailFeignClient;

    /**
     * RedissonClient 注入
     */
    private final RedissonClient redissonClient;

    /**
     * catchOpsComponent 注入
     */
    private final CatchOpsComponent catchOpsComponent;

    /**
     * 线程池
     */
    private final ExecutorService executorService;

    /**
     * 根据SKU ID获取商品详细信息
     *
     * @param skuId 商品SKU ID
     * @return SkuDetailResVo 商品详细信息响应对象
     */
    @Override
    public SkuDetailResVo getSkuDetail(Long skuId) {
        // 获取 sku 信息
        String skuInfoRedisKey = ProductRedisStringConstant.PRODUCT_SKU_INFO_KEY.formatted(skuId);

        // 获取缓存数据
        Optional<SkuDetailResVo> catchData = catchOpsComponent.getCatchData(skuInfoRedisKey, SkuDetailResVo.class);

        if (catchData.isPresent()) {
            return catchData.get();
        }

        // 校验 redis bitMap 布隆过滤器，判断商品SKU ID是否存在
        String skuIdListRedisKey = ProductRedisStringConstant.PRODUCT_SKU_ID_LIST_KEY;
        // 使用Redis的BitMap操作检查SKU ID是否存在
        RBloomFilter<Object> skuIdBloomFilter = redissonClient.getBloomFilter(skuIdListRedisKey);
        boolean isExist = skuIdBloomFilter.contains(skuId);

        // 如果SKU ID不存在于布隆过滤器中，直接返回空的SkuDetailResVo
        if (!isExist) {
            log.warn("SkuId: {} does not exist in the database, returning empty SkuDetailResVo", skuId);
            return null;
        }

        // 注意解决击穿风险
        // 准备一个分布式锁解决击穿。同一个商品只放一个查询进去。注意：设计为细粒度的锁
        String skuLockRedisKey = ProductRedisStringConstant.PRODUCT_SKU_LOCK_KEY.formatted(skuId);
        RLock lock = redissonClient.getLock(skuLockRedisKey);

        // 尝试获取一下锁
        boolean ifLock = lock.tryLock();

        if (!ifLock) {
            try {
                // 如果没有获取到锁，则休眠500毫秒后重试
                TimeUnit.MILLISECONDS.sleep(500);

                // 递归调用 getSkuDetail 方法，直到获取到锁为止
                return catchOpsComponent.getCatchData(skuInfoRedisKey, SkuDetailResVo.class)
                        .orElse(null);
            } catch (InterruptedException _) {
                Thread.currentThread().interrupt();
                return null;
            }
        }

        try {
            // 获取到锁后，再次检查缓存中是否有数据
            Optional<SkuDetailResVo> resVoDto = catchOpsComponent.getCatchData(skuInfoRedisKey, SkuDetailResVo.class);

            if (resVoDto.isPresent()) {
                return resVoDto.get();
            }

            log.info("缓存中没有数据，开始查询数据库，skuId： {}", skuId);

            // 调用getSkuDetailResVo方法获取SKU详细信息
            SkuDetailResVo skuDetailResVo = getSkuDetailResVo(skuId);

            // 如果SKU信息为空，则在Redis中存储一个标记值"x"
            // 将获取到的SKU信息存入Redis缓存
            catchOpsComponent.saveData(skuInfoRedisKey, skuDetailResVo, 7L, TimeUnit.DAYS);

            return skuDetailResVo;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 根据SKU ID获取SKU详情响应对象
     * 该方法通过调用多个远程服务获取SKU的相关信息，并构建一个完整的SKU详情响应对象
     *
     * @param skuId SKU ID，用于查询特定SKU的详细信息
     * @return SkuDetailResVo 包含SKU完整信息的响应对象，包括分类视图、SKU信息、价格、SP
     */
    @MallCache(cacheKey = ProductRedisStringConstant.PRODUCT_SKU_INFO_KEY_PREFIX + "#{#skuId}",
            lockKey = ProductRedisStringConstant.PRODUCT_SKU_LOCK_KEY_PREFIX + "#{#skuId}",
            bloomKey = ProductRedisStringConstant.PRODUCT_SKU_ID_LIST_KEY,
            bloomValue = "#{#skuId}")
    @Override
    public SkuDetailResVo getSkuDetailByAnnotation(Long skuId) {
        return getSkuDetailResVoByAsync(skuId);
    }

    /**
     * 根据SKU ID获取SKU详情响应对象（异步实现）
     * 该方法通过异步调用多个远程服务获取SKU的相关信息，并构建一个完整的SKU详情响应对象
     *
     * @param skuId SKU ID，用于查询特定SKU的详细信息
     * @return SkuDetailResVo 包含SKU完整信息的响应对象，包括分类视图、SKU信息、价格、SPU销售属性列表和SPU值与SKU的JSON映射关系
     */
    private SkuDetailResVo getSkuDetailResVoByAsync(Long skuId) {
        // 创建返回参数
        SkuDetailResVo resVo = SkuDetailResVo.builder().build();

        // 1. 异步查询SKU基本信息，如果为null则提前返回
        CompletableFuture<SkuInfoDto> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            Result<SkuInfoDto> skuInfoResult = skuDetailFeignClient.getSkuInfo(skuId);
            SkuInfoDto skuInfoDto = skuInfoResult.checkSuccess() ? skuInfoResult.getData() : null;

            if (Objects.nonNull(skuInfoDto)) {
                resVo.setSkuInfo(skuInfoDto);
            }
            return skuInfoDto;
        }, executorService);

        // 等待SKU信息获取完成，如果为null则提前返回
        SkuInfoDto skuInfoDto = skuInfoFuture.join();
        if (Objects.isNull(skuInfoDto)) {
            log.warn("不存在此数据，skuId：{}", skuId);
            return null;
        }

        // 2. 并行执行其他查询任务
        // 2.1 异步查询分类视图信息（依赖category3Id）
        CompletableFuture<CategoryViewDto> categoryFuture = CompletableFuture.supplyAsync(() -> {
            Result<CategoryViewDto> categoryViewResult = skuDetailFeignClient.getCategoryView(skuInfoDto.getCategory3Id());
            return categoryViewResult.checkSuccess() ? categoryViewResult.getData() : new CategoryViewDto();
        }, executorService);

        // 2.2 异步查询SKU价格信息
        CompletableFuture<String> priceFuture = CompletableFuture.supplyAsync(() -> {
            Result<String> priceResult = skuDetailFeignClient.getSkuPrice(skuId);
            return priceResult.checkSuccess() ? priceResult.getData() : "0";
        }, executorService);

        // 2.3 异步查询SPU销售属性及值列表（依赖spuId）
        CompletableFuture<List<SpuSaleAttrDto>> saleAttrFuture = CompletableFuture.supplyAsync(() -> {
            Long spuId = Long.valueOf(skuInfoDto.getSpuId());
            Result<List<SpuSaleAttrDto>> spuAttrAndValueResult = skuDetailFeignClient.getSpuSaleAttrAndValue(spuId, skuId);
            return spuAttrAndValueResult.checkSuccess() ? spuAttrAndValueResult.getData() : List.of();
        }, executorService);

        // 2.4 异步查询SPU值与SKU的JSON映射关系（依赖spuId）
        CompletableFuture<String> valueJsonFuture = CompletableFuture.supplyAsync(() -> {
            Long spuId = Long.valueOf(skuInfoDto.getSpuId());
            Result<String> spuValuesSkuJsonResult = skuDetailFeignClient.getSpuValuesSkuJson(spuId);
            return spuValuesSkuJsonResult.checkSuccess() ? spuValuesSkuJsonResult.getData() : "";
        }, executorService);

        // 3. 等待所有异步任务完成
        CompletableFuture.allOf(skuInfoFuture, categoryFuture, priceFuture, saleAttrFuture, valueJsonFuture).join();

        // 返回结果
        return SkuDetailResVo.builder()
                .skuInfo(skuInfoDto)
                .categoryView(categoryFuture.join())
                .price(priceFuture.join())
                .spuSaleAttrList(saleAttrFuture.join())
                .valuesSkuJson(valueJsonFuture.join())
                .build();
    }

    /**
     * 根据SKU ID获取SKU详情响应对象
     * 该方法通过调用多个远程服务获取SKU的相关信息，并构建一个完整的SKU详情响应对象
     *
     * @param skuId SKU ID，用于查询特定SKU的详细信息
     * @return SkuDetailResVo 包含SKU完整信息的响应对象，包括分类视图、SKU信息、价格、SPU销售属性列表等
     */
    private SkuDetailResVo getSkuDetailResVo(Long skuId) {
        // 调用远程服务获取SKU基本信息，如果失败则使用空对象
        Result<SkuInfoDto> skuInfoResult = skuDetailFeignClient.getSkuInfo(skuId);
        SkuInfoDto skuInfoDto = skuInfoResult.checkSuccess() ? skuInfoResult.getData() : null;

        if (Objects.isNull(skuInfoDto)) {
            log.warn("SkuId: {} does not exist in the database", skuId);
            return null;
        }

        // 调用远程服务获取分类视图信息，如果失败则使用空对象
        Result<CategoryViewDto> categoryViewResult = skuDetailFeignClient.getCategoryView(skuInfoDto.getCategory3Id());
        CategoryViewDto categoryViewDto = categoryViewResult.checkSuccess() ? categoryViewResult.getData() : new CategoryViewDto();

        Result<String> priceResult = skuDetailFeignClient.getSkuPrice(skuId);
        // 调用远程服务获取SKU价格信息，如果失败则默认设置为"0"
        String price = priceResult.checkSuccess() ? priceResult.getData() : "0";

        Long spuId = Long.valueOf(skuInfoDto.getSpuId());
        // 获取SPU ID，并调用远程服务获取SPU的销售属性及值列表，如果失败则使用空列表
        Result<List<SpuSaleAttrDto>> spuAttrAndValueResult = skuDetailFeignClient.getSpuSaleAttrAndValue(spuId, skuId);
        List<SpuSaleAttrDto> spuAttrAndValueList = spuAttrAndValueResult.checkSuccess() ? spuAttrAndValueResult.getData() : List.of();

        Result<String> spuValuesSkuJsonResult = skuDetailFeignClient.getSpuValuesSkuJson(spuId);
        String spuValuesSkuJson = spuValuesSkuJsonResult.checkSuccess() ? spuValuesSkuJsonResult.getData() : "";
        // 调用远程服务获取SPU值与SKU的JSON映射关系，如果失败则使用空字符串

        return SkuDetailResVo.builder()
                .categoryView(categoryViewDto)
                // 构建并返回SKU详情响应对象，包含所有获取到的信息
                .skuInfo(skuInfoDto)
                .price(price)
                .spuSaleAttrList(spuAttrAndValueList)
                .valuesSkuJson(spuValuesSkuJson)
                .build();
    }
}
